package com.ruoyi.common.tcp;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.InetSocketAddress;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.mqtts.MqttClientConnect;
import com.ruoyi.common.redis.RedisCache;
import com.ruoyi.common.redis.RedisConst;
import com.ruoyi.common.utils.BytesUtils;
import com.ruoyi.common.utils.StrConverter;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.project.stop.domain.*;
import com.ruoyi.project.stop.service.*;
import com.ruoyi.project.system.dict.service.IDictTypeService;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.swagger.models.auth.In;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    private static Logger log = LoggerFactory.getLogger(NettyClientHandler.class);

    static EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

    /**
     * 建立连接时
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        int port = ipSocket.getPort();
        String host = ipSocket.getHostString();
        log.info("与设备"+host+":"+port+"连接成功!");
        // 重新激活通道
        ctx.fireChannelActive();
        super.channelInactive(ctx);
        log.info("设备"+host+":"+port+"的通道状态 " + ctx.channel().isActive());
    }

    /**
     * channelInactive 被触发一定是和服务器断开了。分两种情况。一种是服务端close，一种是客户端close。
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        int port = ipSocket.getPort();
        String host = ipSocket.getHostString();
        log.error("与设备"+host+":"+port+"连接断开!");
        final EventLoop eventLoop = ctx.channel().eventLoop();
        //断开连接时，将ChannelMap中存储的管道信息删除
        ctx.close();
        super.channelInactive(ctx);
        eventLoopGroup.shutdownGracefully();
        eventLoopGroup = new NioEventLoopGroup();
        //设置断开连接后重连时间，此设置是断开连接一分钟（10秒）后重连
//        Thread.sleep(20000);
//        log.info("开始重新连接"+host+":"+port);
//        NettyClient.channels = NettyClient.connect(host, port);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("关闭客户端连接");
        cause.printStackTrace();
    }
        /**
         * 用来接收心跳检测结果,event.state()的状态分别对应IdleStateHandler(70, 60, 100, TimeUnit.SECONDS)中的
         * 三个参数的时间设置，当满足某个时间的条件时会触发事件
         */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);
        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        int port = ipSocket.getPort();
        String host = ipSocket.getHostString();
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state().equals(IdleState.READER_IDLE)) {
                System.out.println("长期没收到服务器推送数据");
                //重新连接
                NettyClient.connect(host, port);
            } else if (event.state().equals(IdleState.WRITER_IDLE)) {
                System.out.println("长期未向服务器发送数据");
                //发送心跳包
                ByteBuf byteBufOut = StrConverter.getByteBuf("PING/" + ctx.channel().localAddress().toString(), ctx);
                ctx.writeAndFlush(byteBufOut);
            } else if (event.state().equals(IdleState.ALL_IDLE)) {
                System.out.println("ALL");
            }
        }
    }

    /**
     * 业务逻辑处理
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        int port = ipSocket.getPort();
        String host = ipSocket.getHostString();

        ByteBuf byteBuf = (ByteBuf) msg;
        byte[] bytes = new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(bytes);
        String result= BytesUtils.bytesToHexString(bytes);//项目封装的util类

        log.info("接受到 " + host + ":"+port+"的数据: "+ result);

        if(8234 == port  && host.contains("192.168.1.7")){ // 判断是电流计的信息
            /*处理4-20mA进制数据方法*/
            getAiFromHex(result);
        }
        if(8234 == port  && host.contains("192.168.1.9")){ // 判断是电压计的信息
            /*处理0-30V进制数据方法*/
            //getVoFromHex(result);
        }
    }

    private void getVoFromHex(String hexStr){
        /**
         * 数据样例 00010000000701040413220000
         * 字符串的 第18位 ~ 22 位代表通道1（电池电压）
         * 测量电压除以24V 等于当前电量百分比
         * */
        try {
            String voStr = hexStr.substring(18,22);
            Double voDou = (double) Long.parseLong(voStr, 16)/1000;
            Double energyDou = voDou/24*100;
            long energyNum = Math.round(energyDou);
            System.out.println( "电池电压的16进制数组 : " + voStr + " ,转换后为 : " + voDou +"V" + " ,剩余电量为 = " + energyNum + "%");
            // 获取客户端实例
            ConcurrentHashMap<String, MqttClientConnect> mqttClients = MqttClientConnect.mqttClients;
            MqttClientConnect mqttClientConnect = mqttClients.get("1");
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            JSONObject energyJson = new JSONObject();
            energyJson.put("cdate", simpleDateFormat.format(new Date()));
            energyJson.put("energyNum", energyNum+"%");
            mqttClientConnect.pub("energyRecord", energyJson.toJSONString());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    // 处理有人云16进制数据方法
    private void getAiFromHex(String hexStr) {
        try {
            if(hexStr.length()>=82){
                /**
                 * 数据样例 00010000002301042006CB000000000000000000000000000000000000000000000000000000000000
                 * 字符串的 第18位 ~ 22 位代表通道1（温度 18Z1）
                 * 字符串的 第22位 ~ 26 位代表通道2（温度 18Z2）
                 * 字符串的 第26位 ~ 30 位代表通道3（温度 18Z3）
                 * 字符串的 第30位 ~ 34 位代表通道4（温度 18Z4）
                 * 字符串的 第34位 ~ 38 位代表通道5（压强 18Z1）
                 * 字符串的 第38位 ~ 42 位代表通道6（压强 18Z2）
                 * 字符串的 第42位 ~ 46 位代表通道7（压强 18Z3）
                 * 字符串的 第46位 ~ 50 位代表通道8（压强 18Z4）
                 * */
                String tempStrOne = hexStr.substring(18,22);
                /* 实际电流值 = 10进制/249*/
                Double tempAiOne = ((double) Long.parseLong(tempStrOne, 16))/249;
                /** 温度和压强计算公式
                 * 温度 = [（aiValdou - 4）/16]  * 120 -20
                 * 温度 = [（aiValdou - 4）/16]  * 2.5
                 * 实际温度 值 需要+0.5°
                 * 实际压强 值 需要+0.01°
                 * */
                BigDecimal temperatureOne = new BigDecimal(0);
                if(tempAiOne != 0.0){
                    temperatureOne = (new BigDecimal(Double.toString((((tempAiOne - 4)/16)*120 -20))))
                            .setScale(2, RoundingMode.HALF_UP)
                            .add(new BigDecimal(0.5));
                }
                System.out.println( "温度1的16进制数组 : " + tempStrOne + " ,转换后为 : " + tempAiOne +"mA" + " ,温度1 = " + temperatureOne);
                // 温度 18z2
                String tempStrSecond = hexStr.substring(22,26);
                Double tempAiSecond = ((double) Long.parseLong(tempStrSecond, 16))/249;
                BigDecimal temperatureSecond = new BigDecimal(0);
                if(tempAiSecond != 0.0){
                    temperatureSecond = (new BigDecimal(Double.toString((((tempAiSecond - 4)/16)*120 -20))))
                            .setScale(2, RoundingMode.HALF_UP)
                            .add(new BigDecimal(0.5));
                }
                System.out.println( "温度2的16进制数组 : " + tempStrSecond + " ,转换后为 : " + tempAiSecond +"mA" + " ,温度2 = " + temperatureSecond);
                // 温度 18z3
                String tempStrThird = hexStr.substring(26,30);
                Double tempAiThird = ((double) Long.parseLong(tempStrThird, 16))/249;
                BigDecimal temperatureThird = new BigDecimal(0);
                if(tempAiThird != 0.0){
                    temperatureThird = (new BigDecimal(Double.toString((((tempAiThird - 4)/16)*120 -20))))
                            .setScale(2, RoundingMode.HALF_UP)
                            .add(new BigDecimal(0.5));
                }
                System.out.println( "温度3的16进制数组 : " + tempStrThird + " ,转换后为 : " + tempAiThird +"mA" + " ,温度3 = " + temperatureThird);
                // 温度 18z4
                String tempStrFour = hexStr.substring(30,34);
                Double tempAiFour = ((double) Long.parseLong(tempStrFour, 16))/249;
                BigDecimal temperatureFour = new BigDecimal(0);
                if(tempAiFour != 0.0){
                    temperatureFour = (new BigDecimal(Double.toString((((tempAiFour - 4)/16)*120 -20))))
                            .setScale(2, RoundingMode.HALF_UP)
                            .add(new BigDecimal(0.5));
                }
                System.out.println( "温度4的16进制数组 : " + tempStrFour + " ,转换后为 : " + tempAiFour +"mA" + " ,温度4 = " + temperatureFour);
                // 压强 18z1
                String preStrOne = hexStr.substring(34,38);
                Double preAiOne = ((double) Long.parseLong(preStrOne, 16))/249;
                BigDecimal pressureOne = new BigDecimal(0);
                if(preAiOne != 0.0){
                    pressureOne = (new BigDecimal(Double.toString((((preAiOne - 4)/16)*120 -20))))
                            .setScale(2, RoundingMode.HALF_UP)
                            .add(new BigDecimal(0.01));
                }
                System.out.println( "压强1的16进制数组 : " + preStrOne + " ,转换后为 : " + preAiOne +"mA" + " ,压强1 = " + pressureOne);
                // 压强 18z2
                String preStrSecond = hexStr.substring(38,42);
                Double preAiSecond = ((double) Long.parseLong(preStrSecond, 16))/249;
                BigDecimal pressureSecond = new BigDecimal(0);
                if(preAiSecond != 0.0){
                    pressureSecond = (new BigDecimal(Double.toString((((preAiSecond - 4)/16)*120 -20))))
                            .setScale(2, RoundingMode.HALF_UP)
                            .add(new BigDecimal(0.01));
                }
                System.out.println( "压强2的16进制数组 : " + preStrSecond + " ,转换后为 : " + preAiSecond +"mA" + " ,压强2 = " + pressureSecond);
                // 压强 18z3
                String preStrThird = hexStr.substring(42,46);
                Double preAiThird = ((double) Long.parseLong(preStrThird, 16))/249;
                BigDecimal pressureThird = new BigDecimal(0);
                if(preAiThird != 0.0){
                    pressureThird = (new BigDecimal(Double.toString((((preAiThird - 4)/16)*120 -20))))
                            .setScale(2, RoundingMode.HALF_UP)
                            .add(new BigDecimal(0.01));
                }
                System.out.println( "压强3的16进制数组 : " + preStrThird + " ,转换后为 : " + preAiThird +"mA" + " ,压强3 = " + pressureThird);
                // 压强 18z4
                String preStrFour = hexStr.substring(46,50);
                Double preAiFour = ((double) Long.parseLong(preStrFour, 16))/249;
                BigDecimal pressureFour = new BigDecimal(0);
                if(preAiFour != 0.0){
                    pressureFour = (new BigDecimal(Double.toString((((preAiFour - 4)/16)*120 -20))))
                            .setScale(2, RoundingMode.HALF_UP)
                            .add(new BigDecimal(0.01));
                }
                System.out.println( "压强4的16进制数组 : " + preStrFour + " ,转换后为 : " + preAiFour +"mA" + " ,压强4 = " + pressureFour);

                /* 发送传感器数据到mqtt */
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                // 获取客户端实例
                ConcurrentHashMap<String, MqttClientConnect> mqttClients = MqttClientConnect.mqttClients;
                MqttClientConnect mqttClientConnect = mqttClients.get("1");
                JSONObject senjorJson = new JSONObject();
                senjorJson.put("cdate", simpleDateFormat.format(new Date()));
                senjorJson.put("temperatureOne", temperatureOne);
                senjorJson.put("temperatureSecond",  temperatureSecond);
                senjorJson.put("temperatureThird",  temperatureThird);
                senjorJson.put("temperatureFour",  temperatureFour);
                senjorJson.put("pressureOne",  pressureOne);
                senjorJson.put("pressureSecond",  pressureSecond);
                senjorJson.put("pressureThird",  pressureThird);
                senjorJson.put("pressureFour",  pressureFour);
                System.out.println(senjorJson);
                mqttClientConnect.pub("sensorRecord", senjorJson.toJSONString());

                /**
                 * 查询 —— 最新船舶信息数据（redis）
                 * 插入 —— 传感器存入数据库*/
                IShipRecordService shipRecordService = SpringUtils.getBean(IShipRecordService.class);
                ISensorRecordService sensorRecordService = SpringUtils.getBean(ISensorRecordService.class);
                ShipRecord shipRecord = shipRecordService.selectShipRecordByIdLast();
                SensorRecord sensorRecord = new SensorRecord();
                sensorRecord.setShipId(shipRecord.getId());
                sensorRecord.setTemperatureOne(temperatureOne);
                sensorRecord.setTemperatureSecond(temperatureSecond);
                sensorRecord.setTemperatureThird(temperatureThird);
                sensorRecord.setTemperatureFour(temperatureFour);
                sensorRecord.setPressureOne(pressureOne);
                sensorRecord.setPressureSecond(pressureSecond);
                sensorRecord.setPressureThird(pressureThird);
                sensorRecord.setPressureFour(pressureFour);
                sensorRecord.setCreateTime(new Date());
                sensorRecordService.insertSensorRecord(sensorRecord);

                /**
                 * 查询 —— 最新报警阈值设置（redis）
                 * 插入 —— 历史报警记录存入数据库*/
                this.earlyWarning(shipRecord.getId(), shipRecord.getShipName(), temperatureOne, temperatureSecond, temperatureThird, temperatureFour
                , pressureOne, pressureSecond, pressureThird, pressureFour, mqttClientConnect);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }


    public void earlyWarning(Long shipId, String shipName, BigDecimal temperatureOne, BigDecimal temperatureSecond, BigDecimal temperatureThird, BigDecimal temperatureFour
                , BigDecimal pressureOne, BigDecimal pressureSecond, BigDecimal pressureThird, BigDecimal pressureFour, MqttClientConnect mqttClientConnect){
        try{
            /*首先判断有无数据超出异常*/
            // 获取报警阈值 设置值
            IWarnConfigService warnConfigService = SpringUtils.getBean(IWarnConfigService.class);
            WarnConfig warnConfig = warnConfigService.selectWarnConfigById(1L);
            BigDecimal preMax = warnConfig.getPressureMax();
            BigDecimal preMaxSecond = warnConfig.getPressureMaxSecond();
            //BigDecimal tepMax = warnConfig.getTemperatureMax();
            /*压力大于一级时，仅报警*/
            if(pressureOne.compareTo(preMax) >=0 || pressureSecond.compareTo(preMax) >=0 || pressureThird.compareTo(preMax) >=0 || pressureFour.compareTo(preMax) >=0){
               /* 有数据超出阈值 */
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                Long warnType = 0L;
                Long armNum = 0L;
                BigDecimal warnNum = new BigDecimal(0);
                String warnDetail = "";
                if(pressureOne.compareTo(preMax) >=0){
                    warnType = 5L;
                    armNum = 1L;
                    warnNum = pressureOne;
                    warnDetail = "18Z1压强过高：" + pressureOne + "MPa, "  + simpleDateFormat.format(new Date()) + shipName + " 船 ";
                    this.saveWarnHistoryByTcp(shipId, warnType, armNum, warnNum, warnDetail);
                }
                if(pressureSecond.compareTo(preMax) >=0){
                    warnType = 5L;
                    armNum = 2L;
                    warnNum = pressureSecond;
                    warnDetail = "18Z2压强过高：" + pressureSecond + "MPa, "  + simpleDateFormat.format(new Date()) + shipName + " 船 ";
                    this.saveWarnHistoryByTcp(shipId, warnType, armNum, warnNum, warnDetail);
                }
                if(pressureThird.compareTo(preMax) >=0){
                    warnType = 5L;
                    armNum = 3L;
                    warnNum = pressureThird;
                    warnDetail = "18Z3压强过高：" + pressureThird + "MPa, "  + simpleDateFormat.format(new Date()) + shipName + " 船 ";
                    this.saveWarnHistoryByTcp(shipId, warnType, armNum, warnNum, warnDetail);
                }
                if(pressureFour.compareTo(preMax) >=0){
                    warnType = 5L;
                    armNum = 4L;
                    warnNum = pressureFour;
                    warnDetail = "18Z4压强过高：" + pressureFour + "MPa, "  + simpleDateFormat.format(new Date()) + shipName + " 船 ";
                    this.saveWarnHistoryByTcp(shipId, warnType, armNum, warnNum, warnDetail);
                }
            }

            /*自动拍停按钮打开 压力大于二级 连续三次，报警并执行自动拍停指令*/
            IDictTypeService dictTypeService = SpringUtils.getBean(IDictTypeService.class);
            Long autoStatus = dictTypeService.getDictTypeByAutoStop();
            if(autoStatus == 0L){
                if(pressureOne.compareTo(preMaxSecond) >=0 || pressureSecond.compareTo(preMaxSecond) >=0 || pressureThird.compareTo(preMaxSecond) >=0 || pressureFour.compareTo(preMaxSecond) >=0){
                    RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
                    Integer warnSecondNum = 0;
                    String warnSecondNumStr =  JSON.toJSONString(redisCache.getCacheObject(RedisConst.WARN_SECOND_NUM));
                    if(ObjectUtil.isNotEmpty(warnSecondNumStr) &&  !"null".equals(warnSecondNumStr)){
                        warnSecondNum = Integer.valueOf(warnSecondNumStr);
                        warnSecondNum = warnSecondNum + 1;
                    }else {
                        warnSecondNum = warnSecondNum + 1;
                    }
                    // 判断是否连续报警3次
                    if(warnSecondNum >=3){
                        warnSecondNum = 0;
                        /*触发自动拍停*/
                        mqttClientConnect.pub("autoStop","autoStop");
                    }
                    redisCache.setCacheObject(RedisConst.WARN_SECOND_NUM, warnSecondNum, 3, TimeUnit.SECONDS);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void saveWarnHistoryByTcp(Long shipId, Long warnType, Long armNum, BigDecimal warnNum, String warnDetail){
        try {
            // 初始化预警记录实列
            WarnHistory warnHistory = new WarnHistory();
            IWarnHistoryService warnHistoryService = SpringUtils.getBean(IWarnHistoryService.class);
            warnHistory.setWarnDel(0L);
            warnHistory.setShipId(shipId);
            warnHistory.setWarnType(warnType);
            warnHistory.setArmNum(armNum);
            warnHistory.setWarnNum(warnNum);
            warnHistory.setWarnDetail(warnDetail);
            warnHistory.setCreateTime(new Date());
            warnHistoryService.insertWarnHistory(warnHistory);

            /* 如果是异常报警 需要存储急停按钮为 已激活状态 */
            IStopRecordService stopRecordService = SpringUtils.getBean(IStopRecordService.class);
            StopRecordVO stopRecordVO = new StopRecordVO();
            stopRecordVO.setShipId(shipId);
            stopRecordVO.setStopType(0L);
            stopRecordVO.setStopStatus(5L);
            stopRecordVO.setCreateTime(new Date());
            stopRecordService.insertStopRecord(stopRecordVO);

            /* 发送报警数据到mqtt */
            // 获取客户端实例
            ConcurrentHashMap<String, MqttClientConnect> mqttClients = MqttClientConnect.mqttClients;
            MqttClientConnect mqttClientConnect = mqttClients.get("1");
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            JSONObject warnJson = new JSONObject();
            warnJson.put("cdate", simpleDateFormat.format(new Date()));
            warnJson.put("shipId", shipId);
            warnJson.put("warnType",  warnType);
            warnJson.put("armNum",  armNum);
            warnJson.put("warnNum",  warnNum);
            warnJson.put("warnDetail",  warnDetail);
            mqttClientConnect.pub("warnRecord", warnJson.toJSONString());
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}
